﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Antix.Data;

namespace Antix.Data.Catalogue.Objects
{
    /// <summary>
    /// <para>Abstract Database object</para>
    /// <para>Should be used as a base for DBMS specific database objects</para>
    /// </summary>
    public abstract class Database : DatabaseObject
    {
        #region Constructor/Defaults

        /// <summary>
        /// Create a new database
        /// </summary>
        protected Database(string name, string serverName, string connectionString)
            : base(name)
        {
            this.ConnectionString = connectionString;
            this.ServerName = serverName;
            this.IsNew = !this.Exists();
        }

        #endregion

        #region Properties

        /// <summary>
        /// <para>Connection string for this database</para>
        /// </summary>
        public string ConnectionString        {            get;            private set;        }

        /// <summary>
        /// <para>Server name for this database</para>
        /// </summary>
        public string ServerName { get; private set; }

        #region Key

        /// <summary>
        /// Key for this object
        /// </summary>
        public override string Key
        {
            get { return this.Name; }
        }

        #endregion

        #region Tables

        private DatabaseTableCollection _Tables;

        /// <summary>
        /// Tables in this database
        /// </summary>
        public DatabaseTableCollection Tables
        {
            get
            {
                if (this._Tables == null)
                {
                    // load on demand
                    this._Tables = new DatabaseTableCollection(this);
                    this.LoadTables();
                }
                return this._Tables;
            }
        }

        #endregion

        #region Schemas

        private DatabaseSchemaCollection _Schemas;

        /// <summary>
        /// Schemas in this database
        /// </summary>
        public DatabaseSchemaCollection Schemas
        {
            get
            {
                if (this._Schemas == null)
                {
                    // load on demand
                    this._Schemas = new DatabaseSchemaCollection(this);
                    this.LoadSchemas();
                }
                return this._Schemas;
            }
        }

        #endregion

        #region References

        private DatabaseReferenceCollection _References;

        /// <summary>
        /// References
        /// </summary>
        public DatabaseReferenceCollection References
        {
            get
            {
                if (this._References == null)
                {
                    // load on demand
                    this._References = new DatabaseReferenceCollection(this);
                    this.LoadReferences();
                }
                return this._References;
            }
        }

        #endregion

        #endregion

        #region Methods

        #region Exists

        /// <summary>
        /// <para>Check the database exists</para>
        /// </summary>
        /// <returns>True if found</returns>
        public bool Exists()
        {
            return this.Exists(this.ConnectionString);
        }

        /// <summary>
        /// <para>Check the database exists</para>
        /// </summary>
        /// <param name="connectionString">Connection string</param>
        /// <returns>True if found</returns>
        public abstract bool Exists(string connectionString);

        #endregion

        #region Script the database

        /// <summary>
        /// Script this object
        /// </summary>
        /// <returns>SQL</returns>
        public string Script()
        {
            StringBuilder sql = new StringBuilder();
            Script(sql, true);

            return sql.ToString();
        }

        public abstract bool Script(DatabaseColumn column, StringBuilder sql, bool subObjects);

        public abstract bool Script(DatabaseIndex index, StringBuilder sql, bool subObjects);

        public abstract bool Script(DatabaseReference reference, StringBuilder sql, bool subObjects);

        public abstract bool Script(DatabaseSchema schema, StringBuilder sql, bool subObjects);

        public abstract bool Script(DatabaseTable table, StringBuilder sql, bool subObjects);

        #endregion

        #region Load

        /// <summary>
        /// <para>Load all columns on a table</para>
        /// </summary>
        /// <param name="table">Table</param>
        protected internal abstract void LoadColumns(DatabaseTable table);

        /// <summary>
        /// <para>Load a column</para>
        /// </summary>
        /// <param name="table">Table</param>
        /// <param name="name">Name</param>
        /// <param name="typeName">DB Type Name</param>
        /// <param name="length">Length</param>
        /// <param name="allowNull">Allow null</param>
        /// <param name="defaultName">Default constraint name</param>
        /// <param name="defaultValue">Default value</param>
        protected void LoadColumn(DatabaseTable table,
            string name,
            string typeName, int length, bool allowNull,
            string defaultName, string defaultValue)
        {
            table.Columns.Add(new DatabaseColumn(
                name, typeName,
                length, allowNull,
                defaultName, defaultValue,
                table));
        }

        /// <summary>
        /// <para>Load all indexes on a table</para>
        /// </summary>
        /// <param name="table">Table</param>
        protected internal abstract void LoadIndexes(DatabaseTable table);

        /// <summary>
        /// <para>Load an index</para>
        /// </summary>
        /// <param name="table">Table</param>
        /// <param name="name">Name</param>
        /// <param name="columns">Columns</param>
        /// <param name="isUnique">Uniqueness</param>
        protected void LoadIndex(DatabaseTable table, string name, string columns, bool isUnique)
        {
            table.Indexes.Add(new DatabaseIndex(name, columns, isUnique, table));
        }

        /// <summary>
        /// <para>Load all references</para>
        /// </summary>
        protected internal abstract void LoadReferences();

        /// <summary>
        /// <para>Load a reference</para>
        /// </summary>
        /// <param name="name">Reference name</param>
        /// <param name="column">Column</param>
        /// <param name="parentTable">Table</param>
        /// <param name="deleteAction">DeleteAction</param>
        protected void LoadReference(string name, DatabaseColumn column, DatabaseTable parentTable, DeleteActionEnum deleteAction)
        {
            this.References.Add(new DatabaseReference(
                name,
                column, parentTable,
                deleteAction,
                this));
        }

        /// <summary>
        /// <para>Load all schemas</para>
        /// </summary>
        protected internal abstract void LoadSchemas();

        /// <summary>
        /// <para>Load a schema</para>
        /// </summary>
        /// <param name="name">Name of schema</param>
        protected void LoadSchema(string name)
        {
            this.Schemas.Add(new DatabaseSchema(name, this));
        }

        /// <summary>
        /// <para>Load all tables</para>
        /// </summary>
        protected internal abstract void LoadTables();

        /// <summary>
        /// <para>Load a table</para>
        /// </summary>
        /// <param name="schemaName">Name of schema</param>
        /// <param name="name">Name of table</param>
        protected void LoadTable(string schemaName, string name)
        {
            this.Tables.Add(new DatabaseTable(schemaName, name, this));
        }

        #endregion

        #region SetTypeToColumn

        /// <summary>
        /// <para>Set the column properties according to the c# type passed</para>
        /// <para>Note: column.AllowNull is already set</para>
        /// </summary>
        public abstract void SetTypeToColumn(
            Type type, 
            int length, bool variableLength,
            string defaultValue,
            DatabaseColumn column);

        #endregion

        #region ToString

        /// <summary>
        /// <para>String representation of this object</para>
        /// </summary>
        /// <returns>"[Name] on [Server]"</returns>
        public override string ToString()
        {
            return string.Format("{0} on {1}", this.Name, this.ServerName);
        }

        #endregion

        #endregion
    }
}
